package com.hibegin.http.server.impl;

import com.hibegin.http.server.SimpleWebServer;
import com.hibegin.http.server.util.HttpException;
import com.hibegin.http.server.util.HttpMethod;
import com.hibegin.http.server.util.MimeTypeUtil;
import com.hibegin.http.server.web.Interceptor;
import com.szwistar.common.ERR;
import com.szwistar.common.comm.HttpMultipartBodyParser;
import com.szwistar.common.comm.HttpMultipartBodyParser.PartInfo;
import com.szwistar.common.datastruct.Utils;
import com.szwistar.common.os.FileUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;

public class HttpRequestDecoder {
    protected static final Logger LOG = LoggerFactory.getLogger(HttpRequestDecoder.class);
    public static final String CRLF = "\r\n";
    public static final String SPLIT = CRLF + CRLF;
    public static final byte[] CRLF2 = new byte[]{0x0D, 0x0A, 0x0D, 0x0A};
    public static final String MultipartFlag = "multipart/form-data; boundary=";

    // 解码状态定义
    protected static final int STATE_INIT         = 0; // 连接刚建立，或请求已经处理完成，准备接收下一个请求
    protected static final int STATE_HEADER_BEGIN = 1; // 开始接收请求头，请求头还未接收完成
    protected static final int STATE_HEADER_END   = 2; // 请求头接收完成，准备接收 body
    protected static final int STATE_BODY_BEGIN   = 3; // 接收到 body 数据，但 body 还未接收完成
    protected static final int STATE_BODY_END     = STATE_INIT; // body 接收完成(实际上就是回到初始状态)

    protected String name = "未连接";
    protected SimpleWebServer server;
    protected SocketChannel channel;

	// 分配临时缓冲区数据
	ByteBuffer data = ByteBuffer.allocate(4096);

	// 线程是否正在运行
	public volatile Boolean isBusy = false;
    // 解码状态机
    protected int decodeState = STATE_INIT;
    // Header 缓冲区
    protected ByteBuffer headerBytes;
    // Body 缓冲区
    protected ByteBuffer bodyBytes;
    // 需要的 body 数据大小
    protected int requiredBodySize;

    // 解码出来的 HTTP 请求
    protected HttpRequest request;

    @Override
	public String toString() {
    	return "HttpRequestDecoder[" + name + "]";
    }

    public HttpRequestDecoder(SimpleWebServer server, SocketChannel channel) {
    	this.server = server;
    	this.channel = channel;
    	name = channel.socket().getRemoteSocketAddress().toString();
    }

    // 解码状态复位
    protected void reset() {
    	decodeState = STATE_INIT;
    	headerBytes = null;
    	bodyBytes = null;
    	requiredBodySize = 0;
    	request = null;
    }

    public void runDecodeTask() {
    	// 防止多线程无意义重入
    	synchronized(isBusy) {
	    	if(isBusy) { return; }
    	}

        // 在单独的线程中并发执行解码操作
        server.execChannelTask(new Runnable() {
			@Override public void run() {
                decode();
			}
    	});
    }

    protected void decode() {
    	// 防止多线程无意义重入
    	synchronized(isBusy) {
	    	if(isBusy) { return; }
	    	isBusy = true;
    	}

    	// 循环读取通道数据，直到没有数据，或连接已关闭
    	while(true) {
        	try {
                if(!channel.isConnected()) {
                	LOG.debug("{} 连接已断开！", this);
                	break;
                }

        		// 读取数据
	        	int len = server.read(channel, data);
	        	// 如果数据已经读完，则返回
	        	if(len == 0) {
	        		break;
	        	}
	        	// 如果返回 -1，表示 socket 已关闭？
	        	if(len < 0) {
	        		channel.close();
	        		break;
	        	}

	        	// !!! 记得翻转缓冲区，否则不认为有数据 !!!
	        	data.flip();
	        	//LOG.debug("{} Received: {} bytes", this, len);
			} catch (Exception e) {
        		FileUtils.close(channel);
				break;
			}

        	// 循环处理数据，直到所有的数据都被处理完
        	while(data.hasRemaining()) {
        		boolean isRequestReady = false;

                try {
    	        	// 解析请求
                	isRequestReady = doDecode(data);
                } catch (Exception e) {
                    LOG.error("{} 解析请求失败！", this, e);
                    if(channel.isConnected()) {
                    	HttpRequest request = new HttpRequest(server);
                    	HttpResponse response = new HttpResponse(server, channel, request);
                    	ERR code = (e instanceof HttpException) ? ((HttpException)e).getCode() : ERR.SYS;
                        response.renderCode(code, "解析请求失败！\r\n" + e.getMessage());
                    }
                    // 解码状态复位
                    reset();
                    // 关闭连接
                    FileUtils.close(channel);
                    break;
                }

                // 如果请求解码成功，则处理请求
        		if(isRequestReady) {
	                // 创建对应的 HttpResponse 对象
	            	HttpResponse response = new HttpResponse(server, channel, request);
	            	// 请求解析完成，创建应用层处理任务并执行
	            	runAppProcessTask(request, response);
	                // 解码状态复位
	                reset();
        		}
        	}

        	// 清空已经处理的数据，以便下次干净地读取
        	data.clear();
    	}

    	isBusy = false;
    }

    protected static void runAppProcessTask(HttpRequest request, HttpResponse response) {
        try {
        	//LOG.debug("开始处理请求：{} ...", request);
        	// 调用各个应用层拦截器处理请求
            for(Interceptor interceptor : request.getServer().getInterceptors()) {
            	// 如果拦截器处理完成，则退出；否则调用下一个继续处理
                if(!interceptor.doInterceptor(request, response)) {
                	//LOG.debug("请求已经由 {} 处理完成！response = {}", interceptor, response);
                	break;
                }
            }
        }
        catch (Exception e) {
            response.renderCode(ERR.SYS, e.toString());
        }
        finally {
            // 如果不需要保持连接，则关闭连接
        	if(!response.keepAlive()) {
        		//LOG.debug("关闭非 keep-alive 连接：{}", response.channel.socket().getRemoteSocketAddress());
        		FileUtils.close(response.channel);
        	}
        }
    }

    /**
     * 检查新接收到的数据缓冲区中的数据是否能满足 body 数据长度要求
     * @param data 新接收到的数据缓冲区
     * @return >0: 太多了；==0：正好够；<0：不够多
     */
    protected int checkBodyFullfill(ByteBuffer data) {
    	int size = bodyBytes.position() + data.remaining();
    	if(size > requiredBodySize) { return 1; }
    	else if(size < requiredBodySize) { return -1; }
    	else { return 0; }
    }

    /**
     * 数据解码
     * @param byteBuffer
     * @return 是否解码完成
     */
    protected boolean doDecode(ByteBuffer data) throws Exception {
    	//LOG.debug("doDecode: state = {}, bodyLen = {}/{}", decodeState, (bodyBytes == null) ? -1 : bodyBytes.position(), requiredBodySize);
    	//String dataStr = new String(data.array(), data.position(), data.remaining());
    	//System.out.println(dataStr);

    	if(decodeState == STATE_INIT || decodeState == STATE_HEADER_BEGIN) {
    		// 搜索头部结束标志
    		int headerEndPos = Utils.bytesIndexOf(data.array(), data.arrayOffset(), CRLF2);
    		if(headerEndPos < 0) {
    			// 头部没有结束
    			// 分配头部缓冲区(默认长度)
    			if(headerBytes == null) { headerBytes = ByteBuffer.allocate(2048); }
        		// 把数据暂存到头部缓冲区
        		headerBytes.put(data);
    			// 继续等待头部数据
        		decodeState = STATE_HEADER_BEGIN;
    			return false;
    		}

    		// 头部已经结束
    		// 记住数据结束位置，并把结束位置设置为头部结束位置
    		int limit = data.limit(); data.limit(headerEndPos);
			// 分配头部缓冲区(实际长度)
			if(headerBytes == null) { headerBytes = ByteBuffer.allocate(data.remaining()); }
			// 取出头部数据，并暂存到头部缓冲区
			headerBytes.put(data);
			// 状态设置为头部已结束
    		decodeState = STATE_HEADER_END;

            // !!! 创建对应的 HttpRequest 对象 !!!
            request = new HttpRequest(server);
            // 设置远程主机地址
            request.remoteHost = channel.getRemoteAddress();
    		// 解析头部数据
    		parseHeader();

    		// 结束位置设置到最后
    		data.limit(limit);
			// 开始位置指向 body 开始位置
    		data.position(headerEndPos + 4);

    		if(requiredBodySize <= 0) {
    			// 如果需要的 body 长度为 0，则不用接收 body 数据
        		decodeState = STATE_BODY_END;
        		return true;
    		}

			// 等待接收 body 数据 ...
			return false;
    	}

    	if(decodeState == STATE_HEADER_END || decodeState == STATE_BODY_BEGIN) {
    		// 头部处理完成，开始接收 body 数据 ...
    		decodeState = STATE_BODY_BEGIN;

    		// 分配 body 缓冲区
			if(bodyBytes == null) { bodyBytes = ByteBuffer.allocate(requiredBodySize); }

			// 检查缓冲区数据长度情况
			int fullfill = checkBodyFullfill(data);
    		if(fullfill == 0) {
        		// body 正好到齐，数据全部保存到 body 缓冲区
    			bodyBytes.put(data);
    			// body 数据接收完成
        		decodeState = STATE_BODY_END;
            	// 解析 Body
                parseBody();
        		return true;
    		}
    		else if(fullfill > 0) {
        		// body 正好到齐，但还有多余的数据，所以要确保只取需要的数据
    			int limit = data.limit(); data.limit(data.position() + requiredBodySize);
        		// 只保存 body 需要的长度到 body 缓冲区
    			bodyBytes.put(data);
    			// !!! 恢复结束位置(剩余的数据暂时放在缓冲区中，可能是下一个请求的数据) !!!
    			data.limit(limit);
    			// body 数据接收完成
        		decodeState = STATE_BODY_END;
            	// 解析 Body
                parseBody();
        		return true;
    		}
    		else {
    			// body 数据未到齐，数据全部保存到 body 缓冲区
    			bodyBytes.put(data);
    			// 继续等待接收 body 数据 ...
        		return false;
    		}
    	}
		return false;
    }

    /**
     * 解析头部数据
     */
    protected void parseHeader() throws Exception {
    	// 头部数据转换成字符串
    	String headerStr = new String(headerBytes.array());
    	// 把头部用 "\r\n" 分成多行
    	String[] headerLines = headerStr.split("\r\n");

    	// 第一行包含 HTTP 方法、URL 及请求参数信息
    	// GET /test/upload.html HTTP/1.1
    	// CONNECT another.host:80 HTTP/1.1
    	// GET https://another.host:80/index.html HTTP/1.1
        String[] line0Parts = headerLines[0].split(" ");

    	// 解析 HTTP 方法
    	request.method = null;
        for (HttpMethod httpMethod : HttpMethod.values()) {
            if (httpMethod.name().equals(line0Parts[0])) {
                request.method = httpMethod;
                break;
            }
        }
        if (request.method == null) {
            throw new Exception("不支持的 HTTP 方法！" + headerLines[0]);
        }

        // 解析 HTTP 版本号
        request.httpVersion = line0Parts[2];

        // 解析 URI
        String uri = URLDecoder.decode(line0Parts[1], request.getConfig().getCharSet());

        // 如果 URI 是代理地址，则表示是 GET 型代理请求，不支持！
        // 如：GET https://another.host:80/index.html HTTP/1.1
        String uriLowerCase = uri.toLowerCase();
        if (uriLowerCase.startsWith("http://") || uriLowerCase.startsWith("https://")) {
            throw new HttpException(ERR.HTTP_USE_PROXY, "不支持 HTTP 代理！" + headerLines[0]);
        }

        // 解析请求的资源路径和请求参数
        if (uri.contains("?")) {
            request.uri = uri.substring(0, uri.indexOf("?"));
            request.queryStr = uri.substring(uri.indexOf("?") + 1);
            // 解析 Query 字符串中的参数
            parseUrlEncodedStrToMap(request.queryStr);
        } else {
            request.uri = uri;
        }

        // 解析其它的 Header 属性字段
        for (int i = 1; i < headerLines.length; i++) {
            String str = headerLines[i];
            if (str.contains(":")) {
                request.header.put(str.split(":")[0], str.substring(str.indexOf(":") + 1).trim());
            }
        }

        // 取得需要的 body 长度
        requiredBodySize = 0;
        if(request.method == HttpMethod.POST) {
        	try {
                requiredBodySize = Integer.parseInt(request.getHeader("Content-Length"));
			} catch (Exception e) {
	            throw new HttpException(ERR.TOO_FEW_PARAMS, "POST 请求未提供 Content-Length 指示！" + headerLines[0]);
			}
        }

		// 如果需要的 body 长度过长，则抛出异常
		if(requiredBodySize > server.getRequestConfig().getMaxRequestBodySize()) {
			throw new HttpException(ERR.OVERFLOW, "body 长度过大： " + requiredBodySize + " > " + server.getRequestConfig().getMaxRequestBodySize());
		}
    }

    // 解析 URI 或 PostBody(application/x-www-form-urlencoded) 中的 Query 字符串中的参数，并保存到参数表
    protected void parseUrlEncodedStrToMap(String queryString) throws Exception {
        // URL 解码
    	queryString = URLDecoder.decode(queryString, server.getRequestConfig().getCharSet());
    	// 分解各个参数
        String[] args = queryString.split("&");
        for (String arg : args) {
            int idx = arg.indexOf("=");
            if(idx <= 0) { throw new Exception("无法识别的参数：" + arg); }
            String name = arg.substring(0, idx);
            String value = arg.substring(idx + 1);
            request.paramMap.put(name, value);
        }
    }

    // 解析 Body
    protected void parseBody() throws Exception {
    	// 如果没有类型指示，则无法处理 body
    	String contentType = request.getHeader("Content-Type");
        if (contentType == null) { throw new Exception("试图解析未指定的 Content-Type 的 Body ！"); }

        // body 是否是 application/x-www-form-urlencoded 类型数据？
        if ("application/x-www-form-urlencoded".equals(contentType)) {
            // body 转化为字符串
            String body = new String(bodyBytes.array(), server.getRequestConfig().getCharSet());
            parseUrlEncodedStrToMap(body);
            return;
        }

        if(MimeTypeUtil.isText(contentType)) {
        	// body 是文本字符串
        	request.bodyText = new String(bodyBytes.array(), server.getRequestConfig().getCharSet());
            return;
        }

        // body 是否是 multipart/form-data 类型数据？
        int boundaryPos = contentType.indexOf(MultipartFlag);
        if (boundaryPos >= 0) {
        	// 取得 boundary 标志
        	byte[] boundary = contentType.substring(MultipartFlag.length()).getBytes();
        	// 解析出各个 part
        	Map<String, PartInfo> parts = HttpMultipartBodyParser.parse(bodyBytes.array(), boundary);

        	// 把各个 part 分类处理并存放到 HttpRequest 中，以便使用
        	for(PartInfo part: parts.values()) {
        		if(part.filename != null) {
                	// 把 application/octet-stream 型的 part 做为文件 part
        			if(request.fileParts == null) { request.fileParts = new HashMap<String, PartInfo>(); }
        			request.fileParts.put(part.name, part);
        		}
        		else if(part.contentType == null) {
                	// 把其它的 part 中的参数放到参数列表
        			String value = new String(part.data, server.getRequestConfig().getCharSet());
        			request.paramMap.put(part.name, value);
        		}
        		else {
        			throw new Exception("无法识别的 multipart/form-data 类型：" + contentType);
        		}
        	}
            return;
        }

    	// 把 body 做为原始二进制数据
    	request.bodyBytes = bodyBytes.array();
        //throw new Exception("无法解析的 Content-Type：" + contentType);
    }
}
